import { baseParaTagName } from '../config';
import { normalizeRange, selectHead, selectTail, setStartByTextIdx, setEndByTextIdx } from '../range';
import { findCode, findPara, findPreviousPara, findBlock } from '../find';
import { getBoundLetterFromRange, getTextIndexBaseParaFromRange } from '../get';
import { ensureNewLine, isBlockFirstPara, isEmptyNode, isRangeNodeEnd } from '../assert';
import hljs from '../highlight/index';

import { record, revoke } from '../revoke/record';
let recordInputFlag = true, recordDelFlag = true;

function flagRecord(event) {
  if(!isInputEvent(event)) recordInputFlag = true;
  if(!isDelEvent(event)) recordDelFlag = true;

  if(event.key === ' ') {
    record();
  }
  else if(isInputEvent(event)) {
    if(recordInputFlag) {
      recordInputFlag = false;
      record();
    }
  } else if(isDelEvent(event)) {
    if(recordDelFlag) {
      recordDelFlag = false;
      record();
    }
  }
}

const pairMap = new Map([
  ['{', '}'],
  ['(', ')'],
  ['[', ']'],
  ['"', '"'],
  ["'", "'"],
])

let root;
let selection, range;

function syncHeight() {
  window.parent.postMessage(JSON.stringify({
    type: 'changeRTFHeight',
    height: root.scrollHeight
  }))
}

function setRoot(a){
  if(typeof a === 'string') root = document.querySelector(a);
  else if(a instanceof Node) root = a;
  else {
    console.error(`keydown handler set root: root-${root} is not a string or node`);
  }
}

function isDefaultKey(event) {
  if(!(event instanceof KeyboardEvent)) {
    console.warn(`is default key: event-${event} is not keyboardevent, return false`);
    return false;
  }

  const defaultKyes = ['v', 'c', 'z', 'a'];

  return event.ctrlKey && defaultKyes.includes(event.key);
}

function isInputEvent(event) {
  // 'Process' for inputting method
  return event instanceof KeyboardEvent && 
    (event.key.length === 1 || event.key === 'Tab' || event.key === 'Process');
}

function isDelEvent(event) {
  return event instanceof KeyboardEvent && 
    (event.key === 'Backspace');
}

// merge
function mergeNode(node1, node2, flag) {
  if(!flag) {
    while(node2.firstChild) {
      node1.appendChild(node2.firstChild);
    }
    node2.parentNode.removeChild(node2);
  }
  else {
    while(node1.firstChild) {
      node2.insertBefore(node1.lastChild, node2.firstChild);
    }
    node1.parentNode.removeChild(node1);
  }
}

function handlerEnter(event) {
  event.preventDefault();

  if(!range.toString()) {

    const codeBlock = findCode(range);

    let curNode = range.endContainer;
    let curPara = findPara(curNode);
    let curBlock = findBlock(curNode);
    let boundLetters = getBoundLetterFromRange(range);
    range.setEndAfter(curPara);

    let newParaFrag = range.extractContents();

    if(curBlock.tagName.startsWith('H') && isEmptyNode(newParaFrag)) {
      let newPara = document.createElement('P');
      root.insertBefore(newPara, curBlock.nextSibling);
      range.selectNodeContents(newPara);
      range.collapse();
    } else {
      let newPara = newParaFrag.firstChild;

      range.insertNode(newPara);
    
      selectHead(range, newPara);

      if(codeBlock) {
        if(boundLetters[0] === '{' && boundLetters[1] === '}') {
          let blockPara = document.createElement(baseParaTagName);
          newPara.parentNode.insertBefore(blockPara, newPara);
          blockPara.appendChild(document.createTextNode('\t'));
          selectTail(range, blockPara);
        }
        // curPara.innerHTML = hljs.highlight(curPara.innerText, {
        //   language: codeBlock.dataset.lang
        // }).value;
      }
    }
  }

  syncHeight();
}

function handlerBackspace(event) {
  event.preventDefault();

  if(range.toString()) {
    let sc = range.startContainer;
    let so = range.startOffset;
    let ec = range.endContainer;
    let sp = findPara(sc);
    let ep = findPara(ec);
    range.extractContents();
    if(sp !== ep) {
      let range = new Range();
      range.selectNodeContents(ep);
      let frag = range.extractContents();
      sp.appendChild(frag);
      ep.parentNode.removeChild(ep);
    }
    range.setStart(sc, so);
    range.setEnd(sc, so);
  }
  else {
    let node = range.startContainer;
    let para = findPara(node);
    let prePara = findPreviousPara(node, root);
    let head = para.firstChild;

    if(range.startOffset === 0) {
      if(isBlockFirstPara(para) && findBlock(node) !== root) {
        let block = findBlock(node);
        let sib = block.nextSibling;

        while(block.firstChild) {
          block.parentNode.insertBefore(block.firstChild, sib);
        }

        let replacePara = document.createElement(baseParaTagName);
        block.parentNode.insertBefore(replacePara, block);
        block.parentNode.removeChild(block);
        range.selectNodeContents(replacePara);
        range.collapse();
      }
      else if(prePara) {
        let head = para.firstChild
        while(para.firstChild) {
          prePara.appendChild(para.firstChild);
        }
        para.parentNode.removeChild(para);
        if(head) selectHead(range, head);
        else selectTail(range, prePara);
      }
    } else if(range.startOffset === 1) {
      range.setStart(node, 0);
      range.extractContents();
    } else {
      let sc = range.startContainer, so = range.startOffset;
      range.setStart(sc, so - 1);
      range.extractContents();
    }
  }
}

function handlerInput(event) {
  if(event.key.length === 1) {
    handlerInputLetter(event);
  } else if(event.key === 'Tab') {
    handlerInputTab(event);
  }
  // inputting method do nothing
}

function handlerInputLetter(event) {
  event.preventDefault();
  let sc = range.startContainer, ec = range.endContainer,
    sp = findPara(range.startContainer), ep = findPara(range.endContainer);
  normalizeRange(range);
  let boundTextIdx = getTextIndexBaseParaFromRange(range);
  if(sp === ep) {

    let codeBlock = findCode(sc);
    if(codeBlock) {
      if((event.shiftKey && event.key === '{') || event.key === '[' || event.key === '"'
      || event.key === "'" || (event.shiftKey && event.key === '(')) {
        if(range.toString()) {
          let backupRange = range.cloneRange();
          range.collapse(true);
          range.insertNode(document.createTextNode(event.key));
          range.setStart(backupRange.startContainer, backupRange.startOffset);
          range.setEnd(backupRange.endContainer, backupRange.endOffset);
          range.collapse(false);
          range.insertNode(document.createTextNode(pairMap.get(event.key)));
          range.setStart(backupRange.startContainer, backupRange.startOffset);
          range.setEnd(backupRange.endContainer, backupRange.endOffset);
          
          sp.innerHTML = hljs.highlight(sp.innerText, {
            language: codeBlock.dataset.lang
          }).value;

          setStartByTextIdx(range, sp, boundTextIdx[0] + 1);
          setEndByTextIdx(range, sp, boundTextIdx[1] + 1);

          return;
        } else {
          let newTextNode = document.createTextNode(event.key + pairMap.get(event.key));
          range.insertNode(newTextNode);
          setStartByTextIdx(range, sp, boundTextIdx[0] + 1);
          setEndByTextIdx(range, sp, boundTextIdx[1] + 1);
          return;
        }
      }

      range.insertNode(document.createTextNode(event.key))
      sp.innerHTML = hljs.highlight(sp.innerText, {
        language: codeBlock.dataset.lang
      }).value;
      setStartByTextIdx(range, sp, boundTextIdx[0] + 1);
      setEndByTextIdx(range, sp, boundTextIdx[1] + 1);
      return;
    }

    range.extractContents();
    normalizeRange(range);
    let newsc = range.startContainer, newso = range.startOffset;
    
    if(newsc.nodeType === Node.TEXT_NODE) {
      let text = newsc.nodeValue;
      newsc.nodeValue = text.slice(0, newso) + event.key + text.slice(newso);
      range.setStart(newsc, newso + 1);
      range.setEnd(newsc, newso + 1);
    }
    else {
      range.insertNode(document.createTextNode(event.key));
      range.collapse(false);
    }
    return;
  } else {
    range.extractContents();
    let newTextNode =  document.createTextNode(event.key);
    sp.appendChild(newTextNode);
    mergeNode(sp, ep);
    sp.normalize();
    selectTail(range, newTextNode);
  }
}

function handlerInputTab(event) {
  event.preventDefault();
  let sc = range.startContainer, ec = range.endContainer;
  if(sc === ec) {
    range.extractContents();
    range.insertNode(document.createTextNode('\t'));
    range.collapse(false);
    return;
  } else {
    range.extractContents();
    let newTextNode =  document.createTextNode('\t');
    sc.appendChild(newTextNode);
    mergeNode(sc, ec);
    selectTail(range, newTextNode);
  }
}

function handlerComment(event) {
  event.preventDefault();
  let sc = range.startContainer, ec = range.endContainer,
    so = range.startOffset, eo = range.endOffset;

  if(range.toString()) {
    let sp = findPara(sc), ep = findPara(ec);
    let codeBlock = findCode(range);
    if(codeBlock) {
      let para = sp;
      while(para && para != ep.nextSibling) {
        if(para.innerText.startsWith('//')) {
          para.innerHTML = hljs.highlight(para.innerText.replace(/^\/\/\s*/, ''), {
            language: codeBlock.dataset.lang
          }).value;
        } else {
          para.insertBefore(document.createTextNode("// "), para.firstChild);
          para.innerHTML = hljs.highlight(para.innerText, {
            language: codeBlock.dataset.lang
          }).value;
        }
        para = para.nextSibling;
      }
      range.setStartBefore(sp);
      range.setEndAfter(ep);
    }
  } else {
    let para = findPara(sc);
    let codeBlock = findCode(para);
    if(codeBlock) {
      if(para.innerText.startsWith('//')) {
        para.innerHTML = hljs.highlight(para.innerText.replace(/^\/\/\s*/, ''), {
          language: codeBlock.dataset.lang
        }).value;
        range.selectNodeContents(para);
        range.collapse();
      } else {
        para.insertBefore(document.createTextNode("// "), para.firstChild);
        para.innerHTML = hljs.highlight(para.innerText, {
          language: codeBlock.dataset.lang
        }).value;
        range.selectNodeContents(para);
        range.collapse();
      }
    }
  }
}

function keydownHandler(event) {
  if(event.ctrlKey && event.key === 'z') {
    event.preventDefault();
    revoke();
    return;
  }

  if(event.ctrlKey && event.key === 's') {
    event.preventDefault();
    window.parent.postMessage(JSON.stringify({
      type: 'store'
    }));
    return;
  }

  if(event.ctrlKey && event.key === 'a') {
    event.preventDefault();
    range.selectNodeContents(root);
    normalizeRange(range);
    return;
  }

  if(event.ctrlKey && event.key === '/') {
    selection = window.getSelection();
    if(!selection.rangeCount) return;
    range = selection.getRangeAt(0);
    normalizeRange(range);
    handlerComment(event);
    return;
  }

  if(isDefaultKey(event)) return;

  selection = window.getSelection();
  if(!selection.rangeCount) return;
  range = selection.getRangeAt(0);

  flagRecord(event);

  root.focus();
  normalizeRange(range);

  if(event.key === "Enter") {
    handlerEnter(event);
  } 
  else if (event.key === 'Backspace') {
    handlerBackspace(event);
  }
  else if(isInputEvent(event)) {
    handlerInput(event);
  }
  ensureNewLine(root);
}

export {
  setRoot,
  keydownHandler
};